package com.faithbj.process.impl;

import com.faithbj.common.model.CheckTotal;
import com.faithbj.process.*;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * User: xiongyuanwei
 * Date: 12-7-13
 * Time: 上午12:42
 */
@Service
public class ProcessApplicationServiceImpl implements ProcessApplicationService {

    private static Logger log = LoggerFactory.getLogger(ProcessApplicationServiceImpl.class);
    private List<String> groups = Arrays.asList("outer", "inner");

    @Autowired
    private ProcessEngineService processEngineService;

    @Autowired
    private ProcessQueryService processQueryService;

    @Autowired
    private TaskOwnerSelector taskOwnerSelector;

    @Override
    public synchronized void startAndFinishArriveTask(CheckTotal checkTotal) {
        checkNotNull(checkTotal, "checkTotal should not be null.");
        String userId = checkTotal.getHandleUser();
        log.info("用户 {} 启动流程.", userId);
        long processInstanceId = processEngineService.startProcess(ImmutableMap.<String, Object>of("checkTotal", checkTotal));
        log.info("流程号: {}.", processInstanceId);
        long taskId = processQueryService.getTaskIdByProcessInstanceIdAndState(processInstanceId, State.NEW_ARRIVAL);
        switch (checkTotal.getHandleStatus()) {
            case HANDLE:
                completeTask(taskId, checkTotal);
                return;
            case NOT_HANDLE:
                fail(taskId, checkTotal.getHandleMemo());
                log.warn("用户 {} 将任务 #{} 标记失败.", userId, taskId);
        }
    }

    @Override
    public synchronized boolean claimAndGroupStartTask(long taskId) {
        String userId = getUser();
        log.info("用户 {} 接受并开始任务 #{}.", userId, taskId);
        try {
            processEngineService.claim(taskId, userId, groups, new ExcludedOwnerTaskModelFilter(getUser()));
            processEngineService.start(taskId, userId);
            return true;
        } catch (Exception e) {
            log.error("用户 {} 开始任务 #{} 失败.", e);
            return false;
        }
    }

    @Override
    public synchronized void completeTask(long taskId, CheckTotal checkTotal) {
        checkNotNull(checkTotal, "checkTotal should not be null");
        checkNotNull(checkTotal.getRegionId(), "regionId should not be null.");
        String userId = getUser();
        String nextUser = selectNextUser(taskId, checkTotal.getRegionId());
        log.info("用户 {} 完成任务 #{}.", userId, taskId);
        processEngineService.start(taskId, userId);
        processEngineService.complete(taskId, userId, ImmutableMap.<String, Object>of(
                "checkTotal", checkTotal, "nextUser", nextUser));
    }

    @Override
    public synchronized void completeTask(long taskId, CheckTotal checkTotal, Map<String, Object> output) {
        if (checkTotal == null) {
            log.error("No regionId specified, cannot assign task #{} to users.", taskId);
            throw new IllegalArgumentException("No regionId specified.");
        } else {
            checkNotNull(checkTotal.getRegionId(), "regionId should not be null.");
            String userId = getUser();
            String nextUser = selectNextUser(taskId, checkTotal.getRegionId());
            output.put("nextUser", nextUser);
            output.put("checkTotal", checkTotal);
            log.info("用户 {} 完成任务 #{}.", userId, taskId);
            processEngineService.start(taskId, userId);
            processEngineService.complete(taskId, userId, output);
        }
    }

    @Override
    public void completeGroupTask(long taskId, CheckTotal checkTotal) {
        checkNotNull(checkTotal, "checkTotal should not be null");
        String userId = getUser();
        log.info("用户 {} 完成任务 #{}.", userId, taskId);
        processEngineService.complete(taskId, userId, ImmutableMap.<String, Object>of(
                "checkTotal", checkTotal));
    }

    @Override
    public void completeGroupTask(long taskId, CheckTotal checkTotal, Map<String, Object> output) {
        checkNotNull(checkTotal, "checkTotal should not be null");
        String userId = getUser();
        output.put("checkTotal", checkTotal);
        log.info("用户 {} 完成任务 #{}.", userId, taskId);
        processEngineService.complete(taskId, userId, output);
    }

    @Override
    public synchronized void fail(long taskId, String fault) {
        String userId = getUser();
        log.warn("用户 {} 将任务 #{} 标记失败.", userId, taskId);
        processEngineService.start(taskId, userId);
        processEngineService.fail(taskId, userId, fault);
    }

    @Override
    public List<TaskModel> listAllAvailableTasksWithContentByState(State state, int first, int limit) {
        return processQueryService.listAllAvailableTasksWithContentByState(state, first, limit);
    }

    @Override
    public int countAllAvailableTasksByState(State state) {
        return processQueryService.countAllAvailableTasksByState(state);
    }

    @Override
    public List<TaskModel> listAllAvailableTasksWithContent(int first, int limit) {
        return processQueryService.listAllAvailableTasksWithContent(first, limit);
    }

    @Override
    public int countAllAvailableTasks() {
        return processQueryService.countAllAvailableTasks();
    }

    @Override
    public TaskModel getTaskWithContent(long taskId) {
        return processQueryService.getTaskWithContent(taskId);
    }

    @Override
    public List<TaskModel> listAvailableTasksWithContentByUserAndState(State state, int first, int limit) {
        return processQueryService.listAvailableTasksWithContentByUserAndState(getUser(), state, first, limit,
                DefaultTaskModelFilter.getInstance());
    }

    @Override
    public int countAvailableTasksByUserAndState(State state) {
        return processQueryService.countAvailableTasksByUserAndState(getUser(), state,
                DefaultTaskModelFilter.getInstance());
    }

    @Override
    public List<TaskModel> search(CheckTotalCondition condition, State state, int first, int limit) {
        return processQueryService.search(condition, getUser(), state, first, limit, CheckTotalExtractor.getInstance(),
                DefaultTaskModelFilter.getInstance());
    }

    @Override
    public int countSearch(CheckTotalCondition condition, State state) {
        return processQueryService.countSearch(condition, getUser(), state, CheckTotalExtractor.getInstance(),
                DefaultTaskModelFilter.getInstance());
    }

    @Override
    public TaskModel getTaskByProcessInstanceIdAndState(long processInstanceId, State state) {
        Multimap<Long, TaskModel> multimap = processQueryService.getTaskModelsByProcessInstanceIdsAndStates(
                Lists.newArrayList(processInstanceId), Lists.newArrayList(state));
        Collection<TaskModel> tasks = multimap.get(processInstanceId);
        if (tasks != null && !tasks.isEmpty()) {
            return (TaskModel) tasks.toArray()[0];
        } else {
            return null;
        }
    }

    @Override
    public TaskModel getTaskBySiblingTaskAndState(long siblingTaskId, State state) {
        TaskModel task = getTaskWithContent(siblingTaskId);
        return getTaskByProcessInstanceIdAndState(task.getProcessInstanceId(), state);
    }

    private String selectNextUser(long taskId, String regionId) {
        return taskOwnerSelector.nextUser(taskId, regionId);
    }

    private String getUser() {
        return ((UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername();
    }
}
